﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Lilium.LParse.Memoization;

namespace Lilium.LParse
{
    public abstract class ParserState<TToken>
    {
        public ParserState()
        {
        }

        /// <summary>
        /// Appends specified values to excepted values collection ignoring the <see cref="CollectExceptedValues"/> flag.
        /// </summary>
        /// <param name="alternatives"></param>
        /// <remarks>
        /// Этот метод используется в случаях, когда вычисление ожидаемых значений затратно по времени. Он позволяет вручную 
        /// проверять состояние флага сборки ожидаемых значений, и не генерировать перечислитель для них.
        /// 
        /// Надо провести тесты на производительность и постараться избавится от этого метода, он добавляет много грязи без 
        /// особой выгоды.
        /// </remarks>
        public void AppendExceptedValues(IEnumerable<object> alternatives)
        {
            ExceptedValues = Concat(ExceptedValues, alternatives);
        }

        /// <summary>
        /// Discard all errors and restore failed parser state.
        /// </summary>
        /// <remarks>
        /// This method should be called from the <see cref="ParserState&lt;&gt;.Next()">Next()</see> method implementation to allow 
        /// to parse next token.
        /// </remarks>
        public void DiscardErrors()
        {
            Failed = false;
            ExceptedValues = null;
        }

        public void Fail()
        {
            Failed = true;
        }

        public void Fail(object exceptedValue)
        {
            Failed = true;
            if (CollectExceptedValues)
                ExceptedValues = Append(ExceptedValues, exceptedValue);
        }

        public void Fail(IEnumerable<object> alternatives)
        {
            Failed = true;
            if (CollectExceptedValues)
                ExceptedValues = Concat(ExceptedValues, alternatives);
        }
        /*
        /// <summary>
        /// Move parser to failed state and replace excepted values.
        /// </summary>
        /// <param name="alternatives"></param>
        /// <remarks>
        /// Used when affecting parser position (memoisation, look-ahead)
        /// </remarks>
        public void FailAndReplaceExceptedValues(IEnumerable<object> alternatives)
        {
            Failed = true;
            if (CollectExceptedValues)
                ExceptedValues = alternatives;
        }
        */

        /// <summary>
        /// Replaces excepted values.
        /// </summary>
        /// <param name="alternatives">
        /// New excepted values or null to discard currently excepted values.
        /// </param>
        public void ReplaceExceptedValues(IEnumerable<object> alternatives)
        {
            ExceptedValues = alternatives;
        }

        /// <summary>
        /// Cancels failed state. Do not discards excepted values.
        /// </summary>
        public void Restore()
        {
            Failed = false;
        }

        public void ResumeValuesCollection()
        {
            CollectSuspendCount--;

            Debug.Assert(CollectSuspendCount >= 0, "SuspendValuesCollection() / ResumeValuesCollection() not balanced.");
        }

        public void ResumeValuesCollection(object exceptedValue)
        {
            CollectSuspendCount--;

            if (CollectExceptedValues)
                ExceptedValues = Append(ExceptedValues, exceptedValue);

            Debug.Assert(CollectSuspendCount >= 0, "SuspendValuesCollection() / ResumeValuesCollection() not balanced.");
        }

        public void ResumeValuesCollection(IEnumerable<object> alternatives)
        {
            CollectSuspendCount--;

            if (CollectExceptedValues)
                ExceptedValues = Concat(ExceptedValues, alternatives);

            Debug.Assert(CollectSuspendCount >= 0, "SuspendValuesCollection() / ResumeValuesCollection() not balanced.");
        }

        public void SuspendValuesCollection()
        {
            CollectSuspendCount++;
        }

        public Exception GetParserException()
        {
            Debug.Assert(CollectSuspendCount == 0, "SuspendValuesCollection() / ResumeValuesCollection() not balanced.");

            var messageBuilder = new StringBuilder("Unexcepted ");

            if (Eof)
                messageBuilder.Append("[eof]");
            else
            {
                AppendToken(messageBuilder, Current);
                messageBuilder.Append(" at ").Append(ShowPosition());
            }

            if (ExceptedValues != null)
                using (var enumerator = ExceptedValues.GetEnumerator())
                {
                    if (enumerator.MoveNext())
                    {
                        messageBuilder.Append(", excepting ");
                        AppendToken(messageBuilder, enumerator.Current);
                        if (enumerator.MoveNext())
                        {
                            var prev = enumerator.Current;
                            while (enumerator.MoveNext())
                            {
                                messageBuilder.Append(", ");
                                AppendToken(messageBuilder, prev);
                                prev = enumerator.Current;
                            }
                            messageBuilder.Append(" or ");
                            AppendToken(messageBuilder, enumerator.Current);
                        }
                    }
                }

            return new ParseException(messageBuilder.Append('.').ToString());
        }

        #region Bookmarking

        /// <summary>
        /// Bookmarks current position.
        /// </summary>
        /// <returns></returns>
        public abstract long CreateBookmark();

        /// <summary>
        /// Cancels bookmark.
        /// </summary>
        /// <param name="bookmark"></param>
        /// <remarks>
        /// Does not affects excepted values collection or failed state.
        /// </remarks>
        public abstract void CancelBookmark(long bookmark);

        /// <summary>
        /// Restores parser from bookmarked position.
        /// </summary>
        /// <param name="bookmark"></param>
        /// <remarks>
        /// Does not affects excepted values collection or failed state.
        /// </remarks>
        public abstract void RestoreBookmark(long bookmark);

        /// <summary>
        /// Returns bookmark from current position, if bookmark was previously created.
        /// </summary>
        /// <remarks>
        /// This method is used by memoization support to acquire memoized result for current position.
        /// Return value used as key in memoized results store.
        /// </remarks>
        public abstract long CurrentBookmark();

        #endregion

        #region Memoization

        public MemoizationKey CreateMemoizationKey(Delegate parser)
        {
            var currentBookmark = CurrentBookmark();
            return new MemoizationKey(currentBookmark, parser);
        }

        public bool TryGetMemoizedResult(MemoizationKey  key, out MemoizedResult result)
        {
            return MemoizedResults.TryGetValue(key, out result);
        }

        public void MemoizeResult(MemoizationKey key, MemoizedResult result)
        {
            MemoizedResults.Add(key, result);
        }

        #endregion

        public abstract void Next();

        #region public TToken Current

        private TToken m_Current;

        public TToken Current
        {
            get { return m_Current; }
            protected set { m_Current = value; }
        }

        #endregion

        #region public bool CollectExceptedValues

        public bool CollectExceptedValues
        {
            get
            {
                return m_CollectSuspendCount == 0;
            }
        }

        #endregion

        #region public bool Eof

        private bool m_Eof;

        public bool Eof
        {
            get { return m_Eof; }
            protected set { m_Eof = value; }
        }

        #endregion

        #region public IEnumerable<object> ExceptedValues

        private IEnumerable<object> m_ExceptedValues;

        public IEnumerable<object> ExceptedValues
        {
            get { return m_ExceptedValues; }
            private set { m_ExceptedValues = value; }
        }

        #endregion

        #region public bool Failed

        private bool m_Failed;

        public bool Failed
        {
            get { return m_Failed; }
            private set { m_Failed = value; }
        }

        #endregion

        protected abstract string ShowPosition();

        private static void AppendToken(StringBuilder stringBuilder, object token)
        {
            stringBuilder.Append("`").Append(token).Append("'");
        }

        private static IEnumerable<object> Append(IEnumerable<object> source, object item)
        {
            if (source != null)
                foreach (var current in source)
                    yield return current;

            yield return item;
        }

        private static IEnumerable<object> Concat(IEnumerable<object> init, IEnumerable<object> tail)
        {
            if (init != null)
                foreach (var current in init)
                    yield return current;

            if (tail != null)
                foreach (var current in tail)
                    yield return current;
        }

        #region public int CollectSuspendCount

        private int m_CollectSuspendCount;

        public int CollectSuspendCount { get { return m_CollectSuspendCount; } set { m_CollectSuspendCount = value; } }

        #endregion

        #region private Dictionary<MemoizedKey, MemoizedResult> MemoizedResults

        private readonly Dictionary<MemoizationKey, MemoizedResult> m_MemoizedResults = new Dictionary<MemoizationKey, MemoizedResult>();

        private Dictionary<MemoizationKey, MemoizedResult> MemoizedResults
        {
            get
            {
                return m_MemoizedResults;
            }
        }

        #endregion
    }
}